home *** CD-ROM | disk | FTP | other *** search
/ CICA 1993 April / CICA MS Windows - April 1993.iso / unzipped / programr / listings / ptv2n5 / asmcoun1.pas next >
Pascal/Delphi Source File  |  1991-09-18  |  23KB  |  619 lines

  1. { ==========================================================================
  2.          Title: AsmCount
  3.     Programmer: David Neal Dubois
  4.  
  5.    Description: Unit AsmCount provides objects for word counting.
  6.                 Three objects are provided: CountObj, which counts
  7.                 words in an ASCII file; SprintCountObj, which can count
  8.                 words in a file created by Borland's Sprint word
  9.                 processor; and AmiCountObj, designed to count words
  10.                 in AmiPro files.
  11.  
  12.                 A word is defined as a sequence of non-separators. The
  13.                 non-separators are the letters, digits, and apostrophe.
  14.                 The counters for Sprint and AmiPro parse for control code
  15.                 sequences. A control code is treated as a separator.
  16.  
  17.                 The formats in which Sprint and AmiPro store control codes
  18.                 were determined emperically by David Gerrold. Sprint
  19.                 brackets control codes by ASCII codes 15 and 14, (Ctrl-O and
  20.                 Ctrl-N). Control codes may be nested. AmiPro indicates the
  21.                 beginning of a control code by one of these two character
  22.                 sequences: '<@', '<+', or '<-'. The code is terminated by
  23.                 a '>'.
  24.  
  25.                 To use the counters, first initialize a *CountObj using the
  26.                 Init constructor passing the file name as a parameter.
  27.                 Then call the Count method. When the count method returns
  28.                 the field WordCount will hold the number of words in the
  29.                 file. Example:
  30.  
  31.                     var
  32.                       Counter : CountObj;
  33.                     begin
  34.                       with Counter do
  35.                         begin
  36.                           Init ( 'Test' );
  37.                           Count;
  38.                           writeln ( 'number of words: ', WordCount );
  39.                           Done;
  40.                         end;
  41.                     end.
  42.  
  43.                 The Count methods are written in assembly language for
  44.                 maximum speed. Counting the words in a one megabyte ASCII
  45.                 text file stored on RAM disk on a 386, 25 MHz machine takes
  46.                 about 1.5 seconds.
  47.  
  48.         Method: Count reads from the file in blocks of 65520 characters at
  49.                 a time. As each character is processed, Count looks checks
  50.                 a look-up table to determine whether the character is a
  51.                 separator. When a non-separator is found followed by a
  52.                 separator, a word is counted.
  53.   ========================================================================== }
  54.  
  55. unit AsmCoun1;
  56.  
  57. interface
  58.  
  59.   { -----------------------------------------------------------------------
  60.     BlockSize    - The maximum number of characters to be read from a file
  61.                    at one time. This is set to 65520, the largest block
  62.                    which can be allocated from the Turbo Pascal heap.
  63.  
  64.     Init/Done    - The Init constructor opens the file. The Done destructor
  65.                    closes it.
  66.  
  67.     Count        - This method performs the actual word count.
  68.  
  69.     InitCount    - Used internally by Count. This method initializes the
  70.                    look-up table, ensures the file is at its beginning
  71.                    (in case Count was called twice), and allocates a block
  72.                    of memory from the heap to act as a file buffer.
  73.  
  74.     FiniCount    - Used internally by Count. Disposes of the file buffer.
  75.  
  76.     ReadBlock    - Used by Count to read a block from the file. At this time
  77.                    the ShowProgress method is called.
  78.  
  79.     ShowProgress - Gives the user some indication of the Count method's
  80.                    progress. Here it simply prints a period as each block
  81.                    is read. This could changed to produce a percentage-bar.
  82.     ----------------------------------------------------------------------- }
  83.  
  84.   const
  85.     BlockSize = 65520;
  86.   type
  87.     BlockType = array [ 1 .. BlockSize ] of char;
  88.     BlockPtr  = ^ BlockType;
  89.  
  90.     CountPtr = ^ CountObj;
  91.     CountObj = object
  92.                  TheFile   : file;
  93.                  WordCount : longint;
  94.  
  95.                  constructor Init ( FileName : string );
  96.                  destructor  Done;
  97.  
  98.                  procedure Count;     virtual;
  99.                  procedure InitCount; virtual;
  100.                  procedure FiniCount; virtual;
  101.                  procedure ReadBlock;
  102.                  procedure ShowProgress;
  103.                end;
  104.  
  105.     SprintCountPtr = ^ SprintCountObj;
  106.     SprintCountObj = object ( CountObj )
  107.                        procedure Count;     virtual;
  108.                        procedure InitCount; virtual;
  109.                      end;
  110.  
  111.     AmiCountPtr = ^ SprintCountObj;
  112.     AmiCountObj = object ( CountObj )
  113.                     procedure Count; virtual;
  114.                   end;
  115.  
  116. implementation
  117.  
  118.   { -----------------------------------------------------------------------
  119.     These global variables are used internally by the Count method. Storing
  120.     these in the global data segment simplifies the assembly code which
  121.     accesses them.
  122.  
  123.     Block   - A pointer to the file buffer allocated by InitCount.
  124.  
  125.     Table   - The look-up table used to determine whether a character
  126.               is a separator.
  127.  
  128.     Actual  - When a block is read from a file, the actual number of bytes
  129.               which were read.
  130.  
  131.     SaveBP  - SprintObj.Count uses the BP register internally. It stores
  132.               the original value here for safe keeping.
  133.     ----------------------------------------------------------------------- }
  134.  
  135.   var
  136.     Block  : BlockPtr;
  137.     Table  : array [ char ] of byte;
  138.     Actual : word;
  139.     SaveBP : word;
  140.  
  141.   { -----------------------------------------------------------------------
  142.     ShowProgress - Let the user know that progress is being made.
  143.     ----------------------------------------------------------------------- }
  144.  
  145.   procedure CountObj . ShowProgress;
  146.   begin
  147.    { write ( '.' ); } {Can't use this within a DLL!!! }
  148.   end;
  149.  
  150.   { -----------------------------------------------------------------------
  151.     Init - Open a binary file with a record size of 1 byte.
  152.     ----------------------------------------------------------------------- }
  153.  
  154.   constructor CountObj . Init ( FileName : string );
  155.   begin
  156.     assign ( TheFile, FileName );
  157.     reset  ( TheFile, 1 );
  158.     WordCount := 0;
  159.   end;
  160.  
  161.   { -----------------------------------------------------------------------
  162.     Done - Close the file.
  163.     ----------------------------------------------------------------------- }
  164.  
  165.   destructor CountObj . Done;
  166.   begin
  167.     close ( TheFile );
  168.   end;
  169.  
  170.   { -----------------------------------------------------------------------
  171.     InitCount - performs three tasks.
  172.  
  173.                 [1] Ensure the file pointer is set to the beginning of the
  174.                     file.
  175.  
  176.                 [2] Allocate a file buffer from the heap.
  177.  
  178.                 [3] Generate the look-up table used to determine whether
  179.                     a character is a separator. A separator is indicated
  180.                     by a one stored in the table, while a non-separator
  181.                     is indicated by a zero.
  182.     ----------------------------------------------------------------------- }
  183.  
  184.   procedure CountObj . InitCount;
  185.   var
  186.     C : char;
  187.   begin
  188.     seek ( TheFile, 0 );
  189.  
  190.     new ( Block );
  191.  
  192.     for C := #0 to #255 do
  193.       if C in [ '''', '0' .. '9', 'A' .. 'Z', 'a' .. 'z' ] then
  194.         Table [ C ] := 0
  195.       else
  196.         Table [ C ] := 1;
  197.   end;
  198.  
  199.   { -----------------------------------------------------------------------
  200.     FiniCount - Dispose of the file buffer.
  201.     ----------------------------------------------------------------------- }
  202.  
  203.   procedure CountObj . FiniCount;
  204.   begin
  205.     dispose ( Block );
  206.   end;
  207.  
  208.   { -----------------------------------------------------------------------
  209.     ReadBlock - Read a block from the file into the buffer. Call
  210.                 ShowProgress so the user knows what's happening.
  211.     ----------------------------------------------------------------------- }
  212.  
  213.   procedure CountObj . ReadBlock;
  214.   begin
  215.     ShowProgress;
  216.     blockread ( TheFile, Block ^, BlockSize, Actual );
  217.   end;
  218.  
  219.   { -----------------------------------------------------------------------
  220.     Count - Count the words in an ASCII text file.
  221.  
  222.       Register usage: AL - indicates whether the current character is a
  223.                            separator. One for separator, zero for
  224.                            non-separator.
  225.                       AH - indicates whether the previous character was a
  226.                            separator.
  227.                       BX - points to look-up table.
  228.                       CX - number of characters left to be processed in
  229.                            buffer.
  230.                    DX:DI - 32-bit word count
  231.                    ES:SI - points to next character to be processed.
  232.     ----------------------------------------------------------------------- }
  233.  
  234.   procedure CountObj . Count;
  235.   var
  236.     TempCount : longint;
  237.   begin
  238.     InitCount;
  239.  
  240.     asm
  241.       cld                      { Clear direction flag. }
  242.  
  243.       xor   DX, DX             { Set word count to zero. }
  244.       mov   DI, DX
  245.  
  246.       mov   AX, 0101h          { Treat beginning of file as separators.   }
  247.  
  248.       call  @CallReadBlock     { Fill file buffer.                        }
  249.  
  250.     @ProcessNormalChar:
  251.       mov   AH, AL             { Set last character separator flag.       }
  252.       seges lodsb              { Read a character from the buffer.        }
  253.       xlat                     { Check look-up table.                     }
  254.       cmp   AX, 0001h          { AX is 1 if this character is a separator }
  255.                                {   and the previous character isn't.      }
  256.       jz    @CountWord         { If this is the case, count a word.       }
  257.       loop  @ProcessNormalChar { Process the next character.              }
  258.       call  @CallReadBlock     { If we've processed the whole buffer then }
  259.       jmp   @ProcessNormalChar {   fill it again.                         }
  260.  
  261.     @CountWord:
  262.       add   DI, 1              { Increment the word counter.              }
  263.       adc   DX, 0
  264.       loop  @ProcessNormalChar { Process the next character.              }
  265.       call  @CallReadBlock
  266.       jmp   @ProcessNormalChar
  267.  
  268.     @CallReadBlock:
  269.       pushf                    { Before calling the ReadBlock method,     }
  270.       push  AX                 {   save current state of registers.       }
  271.       push  DX
  272.       push  DI
  273.     end; { asm }
  274.  
  275.     ReadBlock;                 { Read a block of characters into buffer.  }
  276.  
  277.     asm
  278.       pop   DI                 { Restore registers.                       }
  279.       pop   DX
  280.       pop   AX
  281.       popf
  282.  
  283.       mov   BX, offset Table   { Set BX to point to look-up table.        }
  284.       les   SI, [ Block ]      { Load address of file buffer into ES:SI.  }
  285.       mov   CX, [ Actual ]     { Find out number of characters in buffer. }
  286.       jcxz  @EndOfFile         { If there are no characters in buffer,    }
  287.                                {   then the end of file has been reached. }
  288.       retn                     { Return from CallReadBlock.               }
  289.  
  290.     @EndOfFile:
  291.       add   SP, 2              { Pop and ignore CallReadBlock's return    }
  292.                                {   address.                               }
  293.  
  294.       cmp   AL, 0              { Special consideration must be given to   }
  295.       jnz   @Fini              {   the end-of-file. If the last character }
  296.       add   DI, 1              {   processed was not a separator, count   }
  297.       adc   DX, 0              {   a word.                                }
  298.  
  299.     @Fini:
  300.       mov   [ word ptr TempCount     ], DI  { Store the result in         }
  301.       mov   [ word ptr TempCount + 2 ], DX  {   TempCount.                }
  302.     end; { asm }
  303.  
  304.     FiniCount;                 { Dispose of file buffer.                  }
  305.     WordCount := TempCount;    { Return word count.                       }
  306.   end;
  307.  
  308.   { -----------------------------------------------------------------------
  309.     The Sprint counter is similar to the ASCII counter, except that it
  310.     parses for Sprint control codes. Sprint brackets control codes by
  311.     Ctrl-O and Ctrl-N. These control codes may be nested. Processing
  312.     proceeds identically to that of the ASCII counter, until a Ctrl-O is
  313.     found.
  314.  
  315.     When a control code sequence is found, the Sprint counter enters
  316.     another loop. The loop processes characters, keeping count of the
  317.     depth of control codes, adding one each time a ^O is encountered,
  318.     and subtracting one each time a ^N is found. When the count reaches
  319.     zero, the control code is terminated and control returns to normal
  320.     word-counting loop.
  321.     ----------------------------------------------------------------------- }
  322.  
  323.   { -----------------------------------------------------------------------
  324.     InitCount - The Sprint counter's InitCount performs the same functions
  325.                 as the ASCII InitCount, except that a special value is
  326.                 placed in the look-up table for Ctrl-O to indicate the
  327.                 start of control-code processing.
  328.     ----------------------------------------------------------------------- }
  329.  
  330.   procedure SprintCountObj . InitCount;
  331.   begin
  332.     CountObj . InitCount;
  333.     Table [ ^O ] := $FF;
  334.   end;
  335.  
  336.   { -----------------------------------------------------------------------
  337.     Count - Count the words in a Sprint file. Comments annotate differences
  338.             between this and CountObj.Count.
  339.  
  340.     There are two pieces of code in this procedure which are kind of
  341.     tricky. These were designed to speed and simplify processing, but
  342.     they'll require some explanation.
  343.  
  344.      ------------------------------------------------------------------
  345.        Previous char   Current char    AH   AL    Need to do
  346.      ------------------------------------------------------------------
  347.          separator       separator     01   01    nothing
  348.          separator     non-separator   01   00    nothing
  349.          separator        Ctrl-O       01   FF    process control code
  350.        non-separator     separator     00   01    count a word
  351.        non-separator   non-separator   00   00    nothing
  352.        non-separator      Ctrl-O       00   FF    process control code
  353.      ------------------------------------------------------------------
  354.  
  355.     This table shows how the main loop must handle different
  356.     situations that will occur. The focus of this code is to maximum
  357.     speed. Therefore, the codes for the table were carefully chosen so
  358.     that the more common "do nothing" situations can be distinguighed
  359.     from the others with a single comparison. Once it has been
  360.     determined that something must be done, it can be determined without
  361.     another comparison.
  362.  
  363.                    cmp AL, AH
  364.                    ja  @CountOrControlCode
  365.                    <...do-nothing processing...>
  366.  
  367.                  @CountOrControlCode:
  368.                    js  <...control-code processing...>
  369.                    <...count a word...>
  370.  
  371.     The other bit of tricky code is in the control code sequence processing
  372.     loop. It is necessary to add one to a counter if character number 15
  373.     is found, and to subtract one if character 14 is found. Then, the
  374.     loop must be terminated if the count has reached zero. This task is
  375.     performed by the following code. (BP holds the depth counter.):
  376.  
  377.                    sub   AL, 14
  378.                    sub   AL, 1
  379.                    adc   BP, 0
  380.                    sub   AL, 1
  381.                    sbb   BP, 0
  382.                    jz    <...loop complete...>
  383.  
  384.     ----------------------------------------------------------------------- }
  385.  
  386.   procedure SprintCountObj . Count;
  387.   var
  388.     TempCount : longint;
  389.   begin
  390.     InitCount;
  391.  
  392.     asm
  393.       cld
  394.  
  395.       mov   [ SaveBP ], BP     { When it comes time to process a control  }
  396.                                {   code sequence, the depth counter is    }
  397.                                {   stored in BP. The original value of BP }
  398.                                {   must be restored later when the method }
  399.                                {   is complete, and also before ReadBlock }
  400.                                {   is called.                             }
  401.  
  402.       xor   DX, DX
  403.       mov   DI, DX
  404.       mov   AX, 0101h
  405.       call  @CallReadBlock
  406.  
  407.     @ProcessNormalChar:
  408.       mov   AH, AL
  409.       seges lodsb
  410.       xlat
  411.     @Continue:
  412.       cmp   AL, AH               { See comments above.                    }
  413.       ja    @CountOrControlCode
  414.  
  415.       loop  @ProcessNormalChar
  416.       call  @CallReadBlock
  417.       jmp   @ProcessNormalChar
  418.  
  419.     @CountOrControlCode:
  420.       js    @ControlCode
  421.  
  422.       add   DI, 1                { Count a word.                          }
  423.       adc   DX, 0
  424.       loop  @ProcessNormalChar
  425.       call  @CallReadBlock
  426.       jmp   @ProcessNormalChar
  427.  
  428.     @ControlCode:
  429.       mov   BP, 1                { Process a control code sequence. A     }
  430.       jmp   @EndControlCodeLoop  {   control sequence begins with ^O and  }
  431.                                  {   ends with ^N. Control codes may be   }
  432.                                  {   nested. Therefore it is necessary to }
  433.                                  {   keep a depth counter. As ^O's are    }
  434.                                  {   processed, the counter will be       }
  435.                                  {   incremented. ^N's will decrement the }
  436.                                  {   counter. When the counter reaches    }
  437.                                  {   zero, the control code is complete.  }
  438.                                  {   The depth counter will be stored in  }
  439.                                  {   BP. Since a ^O has already been      }
  440.                                  {   found, the counter is set to 1.      }
  441.  
  442.     @ProcessControlCodeChar:
  443.       seges lodsb                { Get next character.                    }
  444.  
  445.       sub   AL, 14               { See comments above.                    }
  446.       sub   AL, 1
  447.       adc   BP, 0
  448.       sub   AL, 1
  449.       sbb   BP, 0
  450.       jz    @ControlCodeDone
  451.  
  452.     @EndControlCodeLoop:
  453.       loop  @ProcessControlCodeChar  { Process next char. }
  454.       mov   AL, AH                   { This line is necessary to ensure   }
  455.                                      {   correct processing should the    }
  456.                                      {   end-of-file already be reached.  }
  457.       call  @CallReadBlock
  458.       jmp   @ProcessControlCodeChar
  459.  
  460.     @ControlCodeDone:
  461.        mov  AL, 1                { Return to normal processing as if a    }
  462.        jmp  @Continue            {   a separator had been found.          }
  463.  
  464.     @CallReadBlock:
  465.       pushf
  466.       push  AX
  467.       push  DX
  468.       push  DI
  469.       push  BP                   { Save the depth counter, and restore BP }
  470.       mov   BP, [ SaveBP ]       {   will ReadBlock is called.            }
  471.     end; { asm }
  472.  
  473.     ReadBlock;
  474.  
  475.     asm
  476.       pop   BP
  477.       pop   DI
  478.       pop   DX
  479.       pop   AX
  480.       popf
  481.  
  482.       mov   BX, offset Table
  483.       les   SI, [ Block ]
  484.       mov   CX, [ Actual ]
  485.       jcxz  @EndOfFile
  486.       retn
  487.  
  488.     @EndOfFile:
  489.       pop   CX
  490.       cmp   AL, 1
  491.       jz    @Fini
  492.       add   DI, 1
  493.       adc   DX, 0
  494.  
  495.     @Fini:
  496.       mov   BP, [ SaveBP ]       { Restore original BP.                   }
  497.  
  498.       mov   [ word ptr TempCount     ], DI
  499.       mov   [ word ptr TempCount + 2 ], DX
  500.     end; { asm }
  501.  
  502.     FiniCount;
  503.     WordCount := TempCount;
  504.   end;
  505.  
  506.   { -----------------------------------------------------------------------
  507.     AmiPro control code sequences begin with '<', followed by one of '@',
  508.     '+' or '-'. The sequence terminates with a '>'. AmiCountObj.Count
  509.     processes characters the same as CountObj.Count except that it checks
  510.     each character to see if it is a '<'. When a '<' is encountered,
  511.     another character is read and checked against '@', '+' and '-'. If
  512.     one of these is found, Count enters a loop looking for '>'.
  513.     ----------------------------------------------------------------------- }
  514.  
  515.   procedure AmiCountObj . Count;
  516.   var
  517.     TempCount : longint;
  518.   begin
  519.     InitCount;
  520.  
  521.     asm
  522.       cld
  523.  
  524.       xor   DX, DX
  525.       mov   DI, DX
  526.  
  527.       mov   AX, 0101h
  528.  
  529.       call  @CallReadBlock
  530.  
  531.     @ProcessNormalChar:
  532.       mov   AH, AL
  533.       seges lodsb
  534.  
  535.       cmp   AL, '<'              { Check to see if character is '<'. If   }
  536.       jz    @PerhapsControlCode  {  it is, a control code sequence may be }
  537.                                  {  beginning.                            }
  538.  
  539.     @Continue:
  540.       xlat
  541.       cmp   AX, 0001h
  542.       jz    @CountWord
  543.       loop  @ProcessNormalChar
  544.       call  @CallReadBlock
  545.       jmp   @ProcessNormalChar
  546.  
  547.     @CountWord:
  548.       add   DI, 1
  549.       adc   DX, 0
  550.       loop  @ProcessNormalChar
  551.       call  @CallReadBlock
  552.       jmp   @ProcessNormalChar
  553.  
  554.     @ProcessCharAfterLT:
  555.       seges lodsb                { Read the character that follows '<'.   }
  556.       cmp   AL, '@'              { Check to see if it is '@', '+' or '-'. }
  557.       jz    @ProcessControlCode
  558.       cmp   AL, '+'
  559.       jz    @ProcessControlCode
  560.       cmp   AL, '-'
  561.       jz    @ProcessControlCode
  562.       jmp   @Continue
  563.  
  564.     @PerhapsControlCode:
  565.       loop  @ProcessCharAfterLT
  566.       mov   AL, AH
  567.       call  @CallReadBlock
  568.       jmp   @ProcessCharAfterLT
  569.  
  570.     @ProcessControlCodeChar:
  571.       seges lodsb                { A control code sequence has begun.     }
  572.       cmp   AL, '>'              { Process characters until a '>' is      }
  573.       jz    @Continue            {   found.                               }
  574.  
  575.     @ProcessControlCode:
  576.       loop  @ProcessControlCodeChar
  577.       mov   AL, AH
  578.       call  @CallReadBlock
  579.       jmp   @ProcessControlCodeChar
  580.  
  581.     @CallReadBlock:
  582.       pushf
  583.       push  AX
  584.       push  DX
  585.       push  DI
  586.     end; { asm }
  587.  
  588.     ReadBlock;
  589.  
  590.     asm
  591.       pop   DI
  592.       pop   DX
  593.       pop   AX
  594.       popf
  595.  
  596.       mov   BX, offset Table
  597.       les   SI, [ Block ]
  598.       mov   CX, [ Actual ]
  599.       jcxz  @EndOfFile
  600.       retn
  601.  
  602.     @EndOfFile:
  603.       add   SP, 2
  604.       cmp   AL, 0
  605.       jnz   @Fini
  606.       add   DI, 1
  607.       adc   DX, 0
  608.  
  609.     @Fini:
  610.       mov   [ word ptr TempCount     ], DI
  611.       mov   [ word ptr TempCount + 2 ], DX
  612.     end; { asm }
  613.  
  614.     FiniCount;
  615.     WordCount := TempCount;
  616.   end;
  617.  
  618. {begin}
  619. end.